test(plugin-hono-server): pin the LiteKernel half of the UI auto-discovery guards - #16725
Merged
Merged
Conversation
…overy guards (#16599) The pin file asserted, in prose, that the slug-from-name fallback and the `&& plugin.staticPath` conjunct were dead code once `PluginSchema` began requiring both keys. That was a claim about every entry point argued from one. Measured per kernel, it is false for the second published kernel: - `ObjectKernel.use()` runs `PluginLoader.validatePluginContract` -> `PluginSchema.safeParse` and refuses both inputs (#16334, #16363). The two existing refusal pins are correct and keep their expectations. - `LiteKernel.use()` calls `registerPluginByName` directly and never reaches `PluginLoader`, so both objects are stored verbatim, the block iterates them, and both branches execute. Ablation, from this commit: dropping the `||` moves the mounted route from `/console` to `/undefined`; dropping the `&& plugin.staticPath` conjunct turns an assetless `ui` plugin's clean boot into a `TypeError` naming `paths[1]`, out of `path.resolve(process.cwd(), mount.root)`. Both branches are load-bearing. New group F carries those two readings as permanent pins, with F0 as the firing control that the `LiteKernel` harness can mount at all — so F2's `[]` is caused by the conjunct and not by a harness that never mounts. The three passages that said otherwise now state the fact per kernel, including the `it.todo` case-C narration, whose "the boot path — which never calls `PluginSchema`" is still true for `LiteKernel` and stopped being true for `ObjectKernel` at #16363. No runtime change: one test file, comments and cases only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-trump
marked this pull request as ready for review
September 8, 2026 02:38
os-zhuang
approved these changes
Sep 8, 2026
os-zhuang
deleted the
claude/issue-16599-hono-ui-discovery-unreachable
branch
September 8, 2026 04:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16599
What changed, and why it is not what the card asked for
The card asked for two "unreachable" branches in the hono UI auto-discovery block to be deleted. The dispatch required the premise to be checked first. It was checked per entry point, and it is false for the second published kernel — so nothing was deleted, the reading went back to the seat, and the card was re-scoped (issue comment
5577709701) to what this PR does instead: correct the prose that called those branches dead, and pin the measurement so the tree carries it.No runtime change. One file —
packages/plugins/plugin-hono-server/src/ui-plugin-auto-discovery.pin.test.ts— comments and test cases only.The measurement, per kernel
@objectstack/corepublishes two kernels and the auto-discovery block readsctx.getKernel().pluginson whichever one is hosting it. They do not agree about this block's inputs:type: 'ui'with noslug/ nostaticPathObjectKernel.use()PluginLoader.loadPlugin→validatePluginContract→PluginSchema.safeParse; #16334 made both keys required fortype: 'ui', #16049/#16363 put the schema on the pathLiteKernel.use()registerPluginByNamedirectly and never reachesPluginLoader—git show --stat 2025b1f7e(#16363) touches nolite-kernel.tsSo the contract change the card names, #16334, narrowed one entry point.
LiteKernelis exported from the@objectstack/corebarrel (packages/core/src/index.ts), andAGENTS.md's Kernel table names it for "Tests (vitest), serverless, edge (Workers)". With zero in-repotype: 'ui'producers, externally authored plugins are the block's only real callers on either kernel.Entry points checked
ObjectKernel.use()— refuses.PLUGIN_CONTRACT_VIOLATION … at 'slug': PLUGIN_UI_REQUIRED_KEY_MISSING.LiteKernel.use()— reaches both branches. Both inputs are ordinary type-legalPluginvalues (tsc --noEmit --strictexit 0 with no casts; negative control exit 2,TS2322).ObjectKernelBasesubclasses — none (git grep 'extends ObjectKernelBase'= 1 hit,lite-kernel.ts:24).pluginsmap — reaches (privateis erased at runtime). Not a supported API; not relied on.ctx.getKernel()— reaches; that is what the block's Array /Object.valuesarms are for.getKernel()providers — none beyond the two kernels.Both branches are load-bearing, not merely reachable
Measured by ablation, and the readings are what justify the new pins:
||inplugin.slug || plugin.name.split('/').pop()moves the mounted route from/consoleto/undefined.&& plugin.staticPathconjunct turns an assetlessuiplugin's clean boot intoTypeError: The "paths[1]" argument must be of type string. Received undefined, out ofpath.resolve(process.cwd(), mount.root)further downstart().What this PR adds
Group F, three cases on
LiteKernel, turning those one-off ablation readings into pins the tree keeps:uiplugin mounts on this kernel too. Without it, F2's[]would be indistinguishable from a harness that never mounts here.slug: the fallback derivesconsolefrom@os-fixture/console. The stringconsoleappears in no fixture field, only in the tail ofname, so nothing but the fallback can satisfy it.staticPath: the guard skips the plugin andstart()resolves. The second half is the point; the conjunct is the difference between a clean boot and a crashed one.Falsifier run, from this commit (each leg: anchored mutation, on-disk blob proof,
traprestore verified by blob equality withHEADand an emptygit diff HEAD):&& plugin.staticPathTypeError: The "paths[1]" argument must be of type string. Received undefined; 16 passed, 1 todoEach leg reddens exactly its own pin, so the two are specific rather than a blanket break.
Three prose corrections, each previously asserting deadness with no case behind it:
ObjectKernelsince spec:PluginSchemamakesstaticPath/slugreally required fortype: "ui"(superRefine), and core'sPlugininterface derives fromPluginDefinition(spec half of #16049) #16334, live and load-bearing onLiteKernel, pinned by F1.it.todocase-C narration — "the boot path — which never callsPluginSchema" named no kernel. It is still true forLiteKerneland stopped being true forObjectKernelat feat(core): enforce PluginSchema at kernel.use() (#16049) #16363.The file header now states the two-kernel split once, so "reachable" is written as a property of a branch per kernel.
For whoever dispatches #15638
⭐ #15638's subject is likewise only half dead, and this PR does not touch it. Its
ui-plugindisjunct is refused byObjectKernel.use()since #16363 (PLUGIN_CONTRACT_VIOLATION … at 'type') but still accepted byLiteKernel.use(), where the block still mounts/slugand/slug/*— measured. Theit.todoin this file now says so, and its two candidate pins are spelled againstbootLite. Whoever lands that card owes both halves, exactly as this one did. #15638 is not addressed here and remains open.⛔ Also deliberately not pre-empted here, and carried on the seat's own decision card: whether
LiteKernelshould enforcePluginSchemaat all, and theisDefaultalias two lines from this guard. This PR pins only what the tree does today.Gates
dispatch-gates --tier: no path-derived mandate (the surface hits none of the 3 declared globs).dispatch-gates --commands→ run →--ran:✓ dispatch-gates --ran: 47 derived famil(ies) accounted for — 47 run, 0 NOT-MEASURED, 0 unrun. 45 exit 0.PREREQUISITE NOT MET, which is NOT MEASURED and not a finding —check:dual-build-cjs-loads("this gate reads built output, and some package has no dist/") andcheck:type-check-debt("⛔ This is NOT a pass and NOT a finding: nothing was measured"). Both demand a full workspace build (turbo run build --filter='./packages/*' …) which CI performs before those steps; this is a declared narrowing, and neither gate's subject can move for this diff, which contributes zero bytes to anydist/.pnpm --filter @objectstack/plugin-hono-server test— 23 files, 264 passed, 1 todo.typecheck— exit 0 (both the build program andtsconfig.test.jsonreadsrc/**/*, so the changed file is genuinely in the typechecked surface).Clause ② — derived from the real export surface
No. Mechanically, with firing controls rather than a path test:
hono-plugin.ts= 7 through the same grep).from './hono-plugin'= 8 through the same grep shape).exportlines added or removed by the diff: 0.Nothing enters or leaves the public surface, and the behaviour reading agrees this round for a stronger reason than last: the diff changes no runtime code at all.
Changeset
skip-changeset, and judged rather than assumed —AGENTS.md:1029reserves that label for "a diff that publishes nothing from any released package", so the question is whether this file publishes.package.jsonfiles: ["dist","README.md","CHANGELOG.md"]— no.npmignore.tsup.config.tsentry: ['src/index.ts'], so the bundle cannot reach a test file.dist/, with a firing positive control: a string from the shipped auto-discovery block is present indist/index.jsanddist/index.mjs(2 hits); a string unique to the changed test file is absent (0 hits). An earlier reading taken against an emptydist/was discarded as NOT MEASURED — its control did not fire.pnpm check:published-filesexit 0.⇒ Nothing ships. Had the card's original deletion gone ahead it would have been the opposite call — a breaking changeset on
@objectstack/plugin-hono-server, since for aLiteKernel-hosteduiplugin the FROM/TO would be/lastNameSegment→/undefinedand "boots clean, mounts nothing" → "crashesstart()", plus the ADR-0087 disposition marker.Evidence
Raw logs for every run above, kept at
/tmp/claude-0/-home-user-objectstack/33fa52ce-0dc9-5d1e-aea9-7f2a955fa345/scratchpad/issue-16599/:probe-source-kept.ts(the throwaway reachability probe, 9 cases),probe1.log,probe2.log,ablate2.sh,f-ablate-slug.log,f-ablate-slug-pin.log,f-ablate-sp.log,f-ablate-sp-pin.log,typeprobe.log,typeprobe-neg.log,pkgtest.log,pkgtypecheck.log,g-cmds2.log,g-results.txt,g-recon.log,gate-1.log…gate-47.log. Every build and test ran in the foreground throughscripts/pm/os-verify-lock.sh; verdicts are read from its printedVERDICTline, never a bare$?.🤖 Generated with Claude Code
https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
Generated by Claude Code